//
//  TestBase.h
//  AlicloudHttpDNS
//
//  Created by ElonChan（地风） on 2017/4/14.
//  Copyright © 2017年 alibaba-inc.com. All rights reserved.
//

#import "XCTestCase+AsyncTesting.h"
#import "objc/runtime.h"

static void *kLoopUntil_Key = "LoopUntil_Key";
static void *kNotified_Key = "kNotified_Key";
static void *kNotifiedStatus_Key = "kNotifiedStatus_Key";
static void *kExpectedStatus_Key = "kExpectedStatus_Key";

@implementation XCTestCase (AsyncTesting)

#pragma mark - Public


- (void)waitForStatus:(XCTAsyncTestCaseStatus)status timeout:(NSTimeInterval)timeout {
    self.notified = NO;
    self.expectedStatus = status;
    self.loopUntil = [NSDate dateWithTimeIntervalSinceNow:timeout];

    NSDate *dt = [NSDate dateWithTimeIntervalSinceNow:0.1];
    while (!self.notified && [self.loopUntil timeIntervalSinceNow] > 0) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                 beforeDate:dt];
        dt = [NSDate dateWithTimeIntervalSinceNow:0.1];
    }

    // Only assert when notified. Do not assert when timed out
    // Fail if not notified
    if (self.notified) {
        XCTAssertEqual(self.notifiedStatus, self.expectedStatus, @"Notified status does not match the expected status.");
    } else {
        XCTFail(@"Async test timed out.");
    }
}

- (void)waitForTimeout:(NSTimeInterval)timeout {
    self.notified = NO;
    self.expectedStatus = XCTAsyncTestCaseStatusUnknown;
    self.loopUntil = [NSDate dateWithTimeIntervalSinceNow:timeout];

    NSDate *dt = [NSDate dateWithTimeIntervalSinceNow:0.1];
    while (!self.notified && [self.loopUntil timeIntervalSinceNow] > 0) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                 beforeDate:dt];
        dt = [NSDate dateWithTimeIntervalSinceNow:0.1];
    }
}

- (void)notify:(XCTAsyncTestCaseStatus)status {
    self.notifiedStatus = status;
    // self.notified must be set at the last of this method
    self.notified = YES;
}

#pragma nark - Object Association Helpers -

- (void)setAssociatedObject:(id)anObject key:(void*)key {
    objc_setAssociatedObject(self, key, anObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)getAssociatedObject:(void*)key {
    id anObject = objc_getAssociatedObject(self, key);
    return anObject;
}

#pragma mark - Property Implementations -

- (NSDate*)loopUntil {
    return [self getAssociatedObject:kLoopUntil_Key];
}

- (void)setLoopUntil:(NSDate*)value {
    [self setAssociatedObject:value key:kLoopUntil_Key];
}

- (BOOL)notified {
    NSNumber *valueNumber = [self getAssociatedObject:kNotified_Key];
    return [valueNumber boolValue];
}

- (void)setNotified:(BOOL)value {
    NSNumber *valueNumber = [NSNumber numberWithBool:value];
    [self setAssociatedObject:valueNumber key:kNotified_Key];
}

- (XCTAsyncTestCaseStatus)notifiedStatus {
    NSNumber *valueNumber = [self getAssociatedObject:kNotifiedStatus_Key];
    return [valueNumber integerValue];
}

- (void)setNotifiedStatus:(XCTAsyncTestCaseStatus)value {
    NSNumber *valueNumber = [NSNumber numberWithInt:(int)value];
    [self setAssociatedObject:valueNumber key:kNotifiedStatus_Key];
}

- (XCTAsyncTestCaseStatus)expectedStatus {
    NSNumber *valueNumber = [self getAssociatedObject:kExpectedStatus_Key];
    return [valueNumber integerValue];
}

- (void)setExpectedStatus:(XCTAsyncTestCaseStatus)value {
    NSNumber *valueNumber = [NSNumber numberWithInt:(int)value];
    [self setAssociatedObject:valueNumber key:kExpectedStatus_Key];
}

@end
